/* Generate the RVV type indexer tables.
   Copyright (C) 2023-2023 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "bconfig.h"
#include "system.h"
#include "errors.h"

#include "coretypes.h"

#include <sstream>
#include <assert.h>
#include <math.h>

std::string
to_lmul (int lmul_log2)
{
  std::stringstream lmul_str;
  if (lmul_log2 >= 0)
    lmul_str << "m";
  else
    {
      lmul_str << "mf";
      lmul_log2 = -lmul_log2;
    }

  lmul_str << (1 << lmul_log2);
  return lmul_str.str ();
}

bool
valid_type (unsigned sew, int lmul_log2, bool float_p)
{
  if (lmul_log2 > 3)
    return false;

  switch (sew)
    {
    case 8:
      return lmul_log2 >= -3 && !float_p;
    case 16:
      return lmul_log2 >= -2 && !float_p;
    case 32:
      return lmul_log2 >= -1;
    case 64:
      return lmul_log2 >= 0;
    default:
      return false;
    }
}

std::string
inttype (unsigned sew, int lmul_log2, bool unsigned_p)
{
  if (!valid_type (sew, lmul_log2, /*float_t*/ false))
    return "INVALID";

  std::stringstream mode;
  mode << "v";
  if (unsigned_p)
    mode << "u";
  mode << "int" << sew << to_lmul (lmul_log2) << "_t";
  return mode.str ();
}

std::string
floattype (unsigned sew, int lmul_log2)
{
  if (!valid_type (sew, lmul_log2, /*float_t*/ true))
    return "INVALID";

  std::stringstream mode;
  mode << "vfloat" << sew << to_lmul (lmul_log2) << "_t";
  return mode.str ();
}

std::string
maskmode (unsigned sew, int lmul_log2)
{
  if (!valid_type (sew, lmul_log2, /*float_t*/ false))
    return "INVALID";

  std::stringstream mode;

  int mlen;
  if (lmul_log2 >= 0)
    mlen = sew / (1 << lmul_log2);
  else
    mlen = sew * (1 << -lmul_log2);

  mode << "vbool" << mlen << "_t";
  return mode.str ();
}

std::string
same_ratio_eew_type (unsigned sew, int lmul_log2, unsigned eew, bool unsigned_p,
		     bool float_p)
{
  if (!valid_type (sew, lmul_log2, float_p))
    return "INVALID";

  int elmul_log2;

  if (sew == eew)
    elmul_log2 = lmul_log2;
  else if (sew > eew)
    elmul_log2 = lmul_log2 - std::log2 (sew / eew);
  else /* sew < eew */
    elmul_log2 = lmul_log2 + std::log2 (eew / sew);

  if (float_p)
    return floattype (eew, elmul_log2);
  else
    return inttype (eew, elmul_log2, unsigned_p);
}

int
main (int argc, const char **argv)
{
  // Require at least one argument.
  if (argc < 2)
    return 1;

  FILE *fp = fopen (argv[1], "w");

  if (!fp)
    return 1;

  fprintf (fp, "/* Generated by genrvv-type-indexer */\n");

  for (unsigned vbool : {64, 32, 16, 8, 4, 2, 1})
    {
      std::stringstream mode;
      mode << "vbool" << vbool << "_t";
      fprintf (fp, "DEF_RVV_TYPE_INDEX (\n");
      fprintf (fp, "  /*VECTOR*/ %s,\n", mode.str ().c_str ());
      fprintf (fp, "  /*MASK*/ %s,\n", mode.str ().c_str ());
      fprintf (fp, "  /*SIGNED*/ INVALID,\n");
      fprintf (fp, "  /*UNSIGNED*/ INVALID,\n");
      for (unsigned eew : {8, 16, 32, 64})
	fprintf (fp, "  /*EEW%d_INDEX*/ INVALID,\n", eew);
      fprintf (fp, "  /*SHIFT*/ INVALID,\n");
      fprintf (fp, "  /*DOUBLE_TRUNC*/ INVALID,\n");
      fprintf (fp, "  /*QUAD_TRUNC*/ INVALID,\n");
      fprintf (fp, "  /*OCT_TRUNC*/ INVALID,\n");
      fprintf (fp, "  /*DOUBLE_TRUNC_SCALAR*/ INVALID,\n");
      fprintf (fp, "  /*DOUBLE_TRUNC_SIGNED*/ INVALID,\n");
      fprintf (fp, "  /*DOUBLE_TRUNC_UNSIGNED*/ INVALID,\n");
      fprintf (fp, "  /*DOUBLE_TRUNC_UNSIGNED_SCALAR*/ INVALID,\n");
      fprintf (fp, "  /*DOUBLE_TRUNC_FLOAT*/ INVALID,\n");
      fprintf (fp, "  /*FLOAT*/ INVALID,\n");
      fprintf (fp, "  /*LMUL1*/ INVALID,\n");
      fprintf (fp, "  /*WLMUL1*/ INVALID,\n");
      for (unsigned eew : {8, 16, 32, 64})
	fprintf (fp, "  /*EEW%d_INTERPRET*/ INVALID,\n", eew);

      for (unsigned lmul_log2_offset : {1, 2, 3, 4, 5, 6})
	{
	  unsigned multiple_of_lmul = 1 << lmul_log2_offset;
	  const char *comma = lmul_log2_offset == 6 ? "" : ",";
	  fprintf (fp, "  /*X%d_INTERPRET*/ INVALID%s\n", multiple_of_lmul,
		   comma);
	}
      fprintf (fp, ")\n");
    }

  // Build for vint and vuint
  for (unsigned sew : {8, 16, 32, 64})
    for (int lmul_log2 : {-3, -2, -1, 0, 1, 2, 3})
      for (bool unsigned_p : {false, true})
	{
	  if (!valid_type (sew, lmul_log2, /*float_t*/ false))
	    continue;

	  fprintf (fp, "DEF_RVV_TYPE_INDEX (\n");
	  fprintf (fp, "  /*VECTOR*/ %s,\n",
		   inttype (sew, lmul_log2, unsigned_p).c_str ());
	  fprintf (fp, "  /*MASK*/ %s,\n", maskmode (sew, lmul_log2).c_str ());
	  fprintf (fp, "  /*SIGNED*/ %s,\n",
		   inttype (sew, lmul_log2, /*unsigned_p*/ false).c_str ());
	  fprintf (fp, "  /*UNSIGNED*/ %s,\n",
		   inttype (sew, lmul_log2, /*unsigned_p*/ true).c_str ());
	  for (unsigned eew : {8, 16, 32, 64})
	    fprintf (fp, "  /*EEW%d_INDEX*/ %s,\n", eew,
		     same_ratio_eew_type (sew, lmul_log2, eew,
					  /*unsigned_p*/ true, false)
		       .c_str ());
	  fprintf (fp, "  /*SHIFT*/ %s,\n",
		   inttype (sew, lmul_log2, /*unsigned_p*/ true).c_str ());
	  fprintf (fp, "  /*DOUBLE_TRUNC*/ %s,\n",
		   same_ratio_eew_type (sew, lmul_log2, sew / 2, unsigned_p,
					false)
		     .c_str ());
	  fprintf (fp, "  /*QUAD_TRUNC*/ %s,\n",
		   same_ratio_eew_type (sew, lmul_log2, sew / 4, unsigned_p,
					false)
		     .c_str ());
	  fprintf (fp, "  /*OCT_TRUNC*/ %s,\n",
		   same_ratio_eew_type (sew, lmul_log2, sew / 8, unsigned_p,
					false)
		     .c_str ());
	  fprintf (fp, "  /*DOUBLE_TRUNC_SCALAR*/ %s,\n",
		   same_ratio_eew_type (sew, lmul_log2, sew / 2, unsigned_p,
					false)
		     .c_str ());
	  fprintf (fp, "  /*DOUBLE_TRUNC_SIGNED*/ INVALID,\n");
	  fprintf (fp, "  /*DOUBLE_TRUNC_UNSIGNED*/ %s,\n",
		   same_ratio_eew_type (sew, lmul_log2, sew / 2, true, false)
		     .c_str ());
	  if (unsigned_p)
	    fprintf (fp, "  /*DOUBLE_TRUNC_UNSIGNED_SCALAR*/ INVALID,\n");
	  else
	    fprintf (fp, "  /*DOUBLE_TRUNC_UNSIGNED_SCALAR*/ %s,\n",
		     same_ratio_eew_type (sew, lmul_log2, sew / 2, true, false)
		       .c_str ());
	  fprintf (fp, "  /*DOUBLE_TRUNC_FLOAT*/ %s,\n",
		   same_ratio_eew_type (sew, lmul_log2, sew / 2, false, true)
		     .c_str ());
	  fprintf (fp, "  /*FLOAT*/ %s,\n",
		   floattype (sew, lmul_log2).c_str ());
	  fprintf (fp, "  /*LMUL1*/ %s,\n",
		   inttype (sew, /*lmul_log2*/ 0, unsigned_p).c_str ());
	  fprintf (fp, "  /*WLMUL1*/ %s,\n",
		   inttype (sew * 2, /*lmul_log2*/ 0, unsigned_p).c_str ());
	  for (unsigned eew : {8, 16, 32, 64})
	    {
	      if (eew == sew)
		fprintf (fp, "  /*EEW%d_INTERPRET*/ INVALID,\n", eew);
	      else
		fprintf (fp, "  /*EEW%d_INTERPRET*/ %s,\n", eew,
			 inttype (eew, lmul_log2, unsigned_p).c_str ());
	    }

	  for (unsigned lmul_log2_offset : {1, 2, 3, 4, 5, 6})
	    {
	      unsigned multiple_of_lmul = 1 << lmul_log2_offset;
	      const char *comma = lmul_log2_offset == 6 ? "" : ",";
	      fprintf (fp, "  /*X%d_VLMUL_EXT*/ %s%s\n", multiple_of_lmul,
		       inttype (sew, lmul_log2 + lmul_log2_offset, unsigned_p)
			 .c_str (),
		       comma);
	    }
	  fprintf (fp, ")\n");
	}
  // Build for vfloat
  for (unsigned sew : {32, 64})
    for (int lmul_log2 : {-3, -2, -1, 0, 1, 2, 3})
      {
	if (!valid_type (sew, lmul_log2, /*float_t*/ true))
	  continue;

	fprintf (fp, "DEF_RVV_TYPE_INDEX (\n");
	fprintf (fp, "  /*VECTOR*/ %s,\n", floattype (sew, lmul_log2).c_str ());
	fprintf (fp, "  /*MASK*/ %s,\n", maskmode (sew, lmul_log2).c_str ());
	fprintf (fp, "  /*SIGNED*/ %s,\n",
		 inttype (sew, lmul_log2, /*unsigned_p*/ false).c_str ());
	fprintf (fp, "  /*UNSIGNED*/ %s,\n",
		 inttype (sew, lmul_log2, /*unsigned_p*/ true).c_str ());
	for (unsigned eew : {8, 16, 32, 64})
	  fprintf (fp, "  /*EEW%d_INDEX*/ %s,\n", eew,
		   same_ratio_eew_type (sew, lmul_log2, eew,
					/*unsigned_p*/ true, false)
		     .c_str ());
	fprintf (fp, "  /*SHIFT*/ INVALID,\n");
	fprintf (
	  fp, "  /*DOUBLE_TRUNC*/ %s,\n",
	  same_ratio_eew_type (sew, lmul_log2, sew / 2, false, true).c_str ());
	fprintf (fp, "  /*QUAD_TRUNC*/ INVALID,\n");
	fprintf (fp, "  /*OCT_TRUNC*/ INVALID,\n");
	fprintf (
	  fp, "  /*DOUBLE_TRUNC_SCALAR*/ %s,\n",
	  same_ratio_eew_type (sew, lmul_log2, sew / 2, false, true).c_str ());
	fprintf (
	  fp, "  /*DOUBLE_TRUNC_SIGNED*/ %s,\n",
	  same_ratio_eew_type (sew, lmul_log2, sew / 2, false, false).c_str ());
	fprintf (
	  fp, "  /*DOUBLE_TRUNC_UNSIGNED*/ %s,\n",
	  same_ratio_eew_type (sew, lmul_log2, sew / 2, true, false).c_str ());
	fprintf (fp, "  /*DOUBLE_TRUNC_UNSIGNED_SCALAR*/ INVALID,\n");
	fprintf (
	  fp, "  /*DOUBLE_TRUNC_FLOAT*/ %s,\n",
	  same_ratio_eew_type (sew, lmul_log2, sew / 2, false, true).c_str ());
	fprintf (fp, "  /*FLOAT*/ INVALID,\n");
	fprintf (fp, "  /*LMUL1*/ %s,\n",
		 floattype (sew, /*lmul_log2*/ 0).c_str ());
	fprintf (fp, "  /*WLMUL1*/ %s,\n",
		 floattype (sew * 2, /*lmul_log2*/ 0).c_str ());
	for (unsigned eew : {8, 16, 32, 64})
	  fprintf (fp, "  /*EEW%d_INTERPRET*/ INVALID,\n", eew);
	for (unsigned lmul_log2_offset : {1, 2, 3, 4, 5, 6})
	  {
	    unsigned multiple_of_lmul = 1 << lmul_log2_offset;
	    const char *comma = lmul_log2_offset == 6 ? "" : ",";
	    fprintf (fp, "  /*X%d_VLMUL_EXT*/ %s%s\n", multiple_of_lmul,
		     floattype (sew, lmul_log2 + lmul_log2_offset).c_str (),
		     comma);
	  }
	fprintf (fp, ")\n");
      }

  return 0;
}
